package org.geoserver.jtilecache.tile;

import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.geoserver.jtilecache.service.Parameters;
import org.geoserver.jtilecache.util.Configuration;
import org.geoserver.jtilecache.cache.*;

public class Layer {

	private static Log log = LogFactory.getLog(org.geoserver.jtilecache.tile.Layer.class);

	private String name;
	private ImageFormat format;
	private Parameters params;
	private Set resolutions;
	private int tileHeight = 256;
	private int tileWidth = 256;

	public Layer(String layername, ImageFormat format, Cache cache) {
		this.name = layername;
		setResolutionsFromConfiguration();
		setParametersFromConfiguration();
		this.format = format;
		
		if(log.isTraceEnabled()) {
			log.trace("Created a new layer: " + this.toString());
		}
	}

	public Layer(Parameters params) {
		this.name = params.getLayer();

		setParametersFromConfiguration();
		this.params.merge(params);

		setResolutionsFromConfiguration();
		this.format = this.params.getImageFormat();

		if(log.isTraceEnabled()) {
			log.trace("Created a new layer: " + this.toString());
		}
	}


	private void setResolutionsFromConfiguration() {
		Configuration config = Configuration.getInstance();
		this.resolutions = config.getResolutionConstraints();
	}

	private void setParametersFromConfiguration() {
		Configuration config = Configuration.getInstance();
		this.params = config.getParameters();
	}

	public Set getResolutions() {
		return resolutions;
	}

	public void setResolutions(Set resolutions) {
		this.resolutions = resolutions;
	}

	public boolean isInResolutions(BBOX bbox) {

		Configuration config = Configuration.getInstance();
		Set resset = config.getResolutionConstraints();

		// If the set is null, then all resolutions are allowed
		if(resset == null) {
			return true;
		}

		Double minres = new Double(calculateMinimumResolution(bbox));
		Double maxres = new Double(calculateMaximumResolution(bbox));

		// Check each resolution in the set
		for(Object obj : this.resolutions) {
			Double res = (Double)obj;

			if(log.isTraceEnabled()) {
				log.trace("Min res: " + minres + " Max res: " + maxres +
						" comparing to set res: " + res);
			}

			if(res > minres && res < maxres) {
				if(log.isDebugEnabled()) {
					log.debug("Resolution is within resolution constraints: " + (Double)res);
				}
				return true;
			}
		}
		// Resolution not found
		return false;
	}

	/**
	 * Returns the minimum resolution bound for the given BBOX
	 * (i.e. the smallest resolution at which pixel change would occur)
	 * @param box
	 * @return
	 */
	private double calculateMinimumResolution(BBOX box) {
		// Add one pixel to each image dimension
		double xres = Math.abs((box.getMaximumX()- box.getMinimumX()) / ((double)(this.format.getWidth() + 1)));
		double yres = Math.abs((box.getMinimumY() - box.getMinimumY()) / ((double)(this.format.getHeight() + 1)));

		// Return the larger of the two dimensions
		return Math.max(xres, yres);
	}

	/**
	 * Returns the maximum resolution bound for the given BBOX
	 * (i.e. the largest resolution at which pixel change would occur)
	 * @param box
	 * @return
	 */
	private double calculateMaximumResolution(BBOX box) {
		// Subtract one pixel to each image dimension
		double xres = Math.abs((box.getMaximumX()- box.getMinimumX()) / ((double)(this.format.getWidth() - 1)));
		double yres = Math.abs((box.getMaximumY() - box.getMinimumY()) / ((double)(this.format.getHeight() - 1)));

		// Return the smaller of the two dimensions
		return Math.min(xres, yres);
	}

	public boolean isInLayer(Parameters params) {

		boolean isinlayer =	this.params.sameLayerAs(params) && isInResolutions(params.getBBOX());

		if(log.isTraceEnabled()) {
			if(isinlayer) {
				log.trace("Parameters are in the layer.");
			} else {
				log.trace("Parameters are not in the layer.");
			}
		}

		return isinlayer;
	}

	public String toString() {
		String rtrn = "Layer name: " + this.name + "; ";
		rtrn += this.params.getReadableString();
		return rtrn;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	public ImageFormat getImageFormat() {
		return format;
	}
	
	public String getMime() {
		return this.format.getMime().getMime();
	}
	
	public String getEncoding() {
		return this.format.getMime().getEncoding();
	}
}
